package com.bt.springboot.testCompeleteDemo.exception;

import io.lettuce.core.RedisException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Description
 * 切换redis
 * @Author weave
 * @Date 2023/10/12 15:40
 */
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class RedisExceptionHandler {
//    @Value("${spring.redis.myself.nodes}")
    String clusterNodes = "";

    @Autowired
    private RedisProperties redisProperties;

    @Autowired
    private ApplicationContext context;

    /**
     * 对redisException进行处理;
     * 根据自己系统，来设置返回值
     * @param ex ex
     * @param request request
     */
    @ExceptionHandler(value = RedisException.class)
    protected synchronized void handleRedisException(Exception ex, HttpServletRequest request){
        String[] split = clusterNodes.split(",");
        // 切换备用服务器
        for (String node : split) {
            if(checkConnection(node)){
                break;
            }
        }
    }

    /**
     * 检测连接问题
     * @param node ip与端口
     * @return boolean
     */
    private boolean checkConnection(String node){

        RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate");
        try{
            redisTemplate.opsForValue().set("testValue","testValue",60, TimeUnit.SECONDS);
            return true;
        }catch (Exception e){
            // 方便查看报错信息与连接信息
            System.out.println("报错了报错了:" + e);
            LettuceConnectionFactory connectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
            RedisStandaloneConfiguration config = connectionFactory.getStandaloneConfiguration();
            System.out.println(config.getHostName()+"---"+config.getPort());

            try{
                String[] split1 = node.split(":");
                switchServer(split1[0],split1[1]);
            }catch (Exception e1){
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 销毁与生成redisTemplate
     * @param host ip
     * @param port 端口
     */
    private void switchServer(String host, String port) {
        RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate");
        if(redisTemplate != null) {
            RedisTemplate<Object, Object> redisTemplateCopy = new RedisTemplate<>();
            BeanUtils.copyProperties(redisTemplate,redisTemplateCopy);
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ((ConfigurableApplicationContext) context).getBeanFactory();
            beanFactory.destroySingleton("redisTemplate");
            beanFactory.registerSingleton("redisTemplate", createRedisTemplate(host, port, redisTemplateCopy));
        }
    }

    /**
     * 创建RedisTemplate
     * @param host ip
     * @param port 端口
     * @param redisTemplate redisTemplate
     * @return 新的redisTemplate
     */
    private RedisTemplate<Object,Object> createRedisTemplate(String host, String port, RedisTemplate<Object, Object> redisTemplate) {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, Integer.valueOf(port));
        configuration.setPassword(redisProperties.getPassword());

        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
        factory.afterPropertiesSet();

        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 是否更换redis源成功
     */
    private void end(){
        Map<String,String> map = new HashMap<>();
        RedisTemplate redisTemplate = (RedisTemplate)context.getBean("redisTemplate");
        String value = (String)redisTemplate.opsForValue().get("testValue");
        if(!StringUtils.isEmpty(value)){
            updateRedisTemplate(redisTemplate);

            // 设置返回值，返回前端，前端根据码值来确定是否再次调用
            map.put("code","1010");
        }
    }

    /**
     * 将包含redisTemplate的bean，更新其redisTemplate
     * @param redisTemplate
     */
    private void updateRedisTemplate(RedisTemplate redisTemplate) {
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            Object bean = null;
            try{
                bean = context.getBean(beanName);
            }catch (Exception e){
                continue;
            }
            /*
                在您的代码中，您正在尝试访问并修改所有具有RedisTemplate类型字段的bean。然而，这可能无法处理一些情况，
                例如当RedisTemplate字段是在父类中定义的，或者当字段是私有的并且在子类中被隐藏的。
                如果RedisTemplate字段是在一个代理类中（例如，当您的服务类被注解为@Transactional时），
                那么您可能无法直接访问该字段。这是因为Spring会为代理类创建一个子类，并且您实际上是在访问这个子类的实例。
                在这种情况下，您需要访问原始的目标类，而不是代理类。
             */
            if(AopUtils.isCglibProxy(bean)){
                try{
                    bean = ((Advised)bean).getTargetSource().getTarget();
                }catch (Exception e){
//                    e.printStackTrace();
                }
            }
            Field[] declaredFields = getAllFields(bean.getClass());

            // 设置新的redisTemplate
            for (Field field : declaredFields) {
                if(field.getType().equals(RedisTemplate.class)){
                    try{
                        boolean accessible = field.isAccessible();
                        field.setAccessible(true);
                        field.set(bean,redisTemplate);
                        field.setAccessible(accessible);
                    }catch (IllegalAccessException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 获取类的所有属性
     * @param clazz
     * @return
     */
    private Field[] getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields.toArray(new Field[0]);
    }
}
